Skip to content

feat: Implement merge queue feature with per-repo opt-in and UI - #121

Merged
Ziinc merged 6 commits into
mainfrom
claude/merge-queue-qa-9b2gmc
Jul 29, 2026
Merged

feat: Implement merge queue feature with per-repo opt-in and UI#121
Ziinc merged 6 commits into
mainfrom
claude/merge-queue-qa-9b2gmc

Conversation

@Ziinc

@Ziinc Ziinc commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR implements a complete merge queue feature for Treq, allowing users to automatically merge branches once CI passes. The feature includes per-repo opt-in controls, a dedicated merge queue tab in the GitHub panel, queue status visualization in workspaces, and comprehensive screenshot tests.

Key Changes

Core Merge Queue Infrastructure

  • Added useMergeQueueEnabled(), useSetMergeQueueEnabled(), and useDequeueBranches() hooks to manage queue state and operations
  • Implemented buildQueueStacks() utility to group flat queue entries into logical stacks, preserving merge order and handling stacked PRs (branches targeting other queued branches)
  • Added Supabase migrations (007_merge_queue_enabled.sql) with RPCs for get_merge_queue_enabled() and set_merge_queue_enabled()

UI Components

  • GitHubIntegrationSettings: Added MergeQueueSetting component with eligibility checks (Pro plan + GitHub App installed) and toggle/enable controls
  • GitHubPanel:
    • Added "Merge Queue" tab displaying queue entries with visual stack hierarchy
    • Implemented queue visualization with vertical timeline, status-colored nodes, and stack grouping
    • Added per-entry and per-stack removal buttons
    • Integrated queue status polling (30s refetch interval)
  • ShowWorkspace:
    • Added "Add to Queue" button for pushing workspaces to the merge queue
    • Integrated queue status display with position tracking and status indicators
    • Added useEnqueueWorkspace() hook for enqueueing operations

Feature Flag & Build Configuration

  • Enabled mergeQueue feature flag in package.json (was previously disabled)
  • All queue functionality is gated behind FEATURES.mergeQueue to prevent unnecessary Supabase polling when disabled

Testing

  • Added comprehensive screenshot specs:
    • merge-queue.spec.tsx: Full workflow from workspace creation through queue states
    • merge-queue-tab.spec.tsx: Queue tab visualization with stacked PRs
    • merge-queue-setting.spec.tsx: Settings page opt-in controls
    • merge-queue-flag-off.spec.tsx: Regression guard ensuring no polling when flag is off
  • Updated integration tests to mock new queue-related hooks

Notable Implementation Details

  • Eligibility Model: Queue opt-in requires Pro plan + GitHub App installation on the repo. Ineligible repos show contextual messaging rather than disabled controls
  • Stack Visualization: Queue entries are grouped into stacks based on target branch relationships, with visual indicators showing merge sequence and dependencies
  • No-Config Default: Repos with no merge queue config row default to OFF, requiring explicit opt-in before any branch can be enqueued
  • Feature Flag Safety: The feature flag gates all Supabase RPC calls, preventing unnecessary polling for users without the feature enabled
  • Status Types: Updated QueueEntryStatus type to use "merging" instead of "passed" to better reflect queue state semantics
    https://claude.ai/code/session_013HAvkSFAyYFkHMAsorqZMc

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

📸 App QA screenshots

Re-ran the 4 flows whose spec this PR adds or modifies — 17 captures. Other specs in the library were not run.

⬇️ Download the screenshots

merge-queue-flag-off
  • merge-queue-flag-off-01-workspace.png
    • The workspace header has no "Add to Queue" button -- only the PR button ("Create PR"), "Merge..." and the overflow menu.
    • The workspace row in the left sidebar has no coloured queue status dot.
    • The workspace is pushed to its remote, so this is the exact state that shows the queue button when the flag is on.
  • merge-queue-flag-off-02-github-panel.png
    • The GitHub panel shows only two tabs, "Issues" and "Pull Requests".
    • There is no "Merge Queue" tab and no PRO upsell for it.
merge-queue-setting
  • merge-queue-setting-01-off.png
    • There is a single "GitHub" header with an icon and a rule under it -- no bordered cards anywhere on the page.
    • Under that header, a "Merge queue" row reads "Merge branches automatically once CI passes." and a "Connected repositories" row sits below it, separated by a thin divider.
    • The merge queue row's control is a primary "Enable merge queue" CTA button -- there is no toggle switch while it is off.
  • merge-queue-setting-02-on.png
    • The merge queue row now reads "Queued branches merge automatically once CI passes."
    • Its control has become a toggle switch in the ON position (filled/primary, knob to the right) -- the CTA button is gone.
    • No error text is shown.
  • merge-queue-setting-03-not-eligible.png
    • The merge queue row explains that the Treq GitHub App must be installed on treq-dev/treq to use the merge queue.
    • There is no Enable CTA and no toggle -- an ineligible repo gets the reason instead of a control that could only fail.
merge-queue-tab
  • merge-queue-tab-01-disabled.png
    • The Merge Queue tab is selected and shows a centred empty state: a merge icon over "The merge queue is off for this repository," with an outlined "Enable it in Settings › Integrations" button below.
    • There is no toggle switch anywhere on this tab -- the opt-in lives in Settings.
    • No queue entries are listed.
  • merge-queue-tab-02-enabled-with-queue.png
  • merge-queue-tab-02b-stacks-and-terminator.png
    • Two "Stack of N" headers appear inline in the list: "Stack of 3 · merges bottom-up into main" above entries 1-3, and "Stack of 2" above entries 5-6. Entry test: e2e testing - no conflict #4 has no stack header.
    • Each stack header has a "Remove stack" button, entries in a stack have a short vertical accent line to their left, and every entry row has its own small X remove button.
    • At the very bottom of the line is a down-arrow and the target branch "main"; the entry with no PR number reads "No PR".
  • merge-queue-tab-03-enabled-empty.png
    • The toggle row reads "Enabled for this repository." with the switch in the ON position.
    • The body shows the "Merge queue is empty." empty state with a merge icon -- distinct from the "off for this repository" state.
  • merge-queue-tab-04-removed-single.png
  • merge-queue-tab-05-removed-stack.png
    • The "Stack of 3" block's Remove stack button has been clicked; the three branches were dequeued top-down.
    • The view is otherwise unchanged since the stubbed backend keeps returning the same queue contents.
merge-queue
  • merge-queue-01-not-queued.png
    • The workspace header shows an outlined "Add to Queue" button with a merge (git-merge) icon.
    • The header also still shows the "Merge..." button next to it.
    • The workspace row in the left sidebar has no coloured status dot next to its branch name.
  • merge-queue-02-queued.png
    • The queue button now reads "Queued" -- no position number -- and is filled/secondary-styled rather than outlined.
    • A green success toast reads "Added to merge queue".
    • The workspace row in the left sidebar shows a small yellow dot next to its branch name.
  • merge-queue-03-testing.png
    • The queue button reads "Queued" -- the header deliberately shows neither a "Testing…" label nor a position number.
    • The sidebar dot for the workspace is blue (CI running) rather than yellow.
  • merge-queue-04-merging.png
    • The entry is in the backend 'merging' state (the queue is landing it now).
    • The queue button reads "Queued".
    • The sidebar dot for the workspace is green (passed CI, merging now) -- not the neutral grey it used to be before 'merging' had a case.
  • merge-queue-05-merged.png
    • Once the entry merges, the button reverts to the outlined "Add to Queue" label.
    • The sidebar dot for the workspace is dark green (merged via queue).
  • merge-queue-06-closed-pr-error.png
    • A red/destructive error toast reads "Queue error" with a description naming the branch and that gh reports CLOSED.
    • The queue button is still the outlined "Add to Queue" -- nothing was enqueued.

Each bullet under a capture is what the spec claims that image should show — open the PNG and check it.


commit 78a690d · workflow run — this comment is updated in place on each run.

claude added 5 commits July 29, 2026 00:18
Drives the real merge queue UI (real jj repo, real Rust dispatch, real React
tree) through each state the backend can emit, capturing the header queue
button and sidebar status dot. Only the Supabase boundary and the gh
subprocess are stubbed, since neither is reachable from the desktop harness.

Covers: not queued, enqueue via the real button, testing, merging, merged,
and the no-open-PR error path.
…uring polling

Enable the mergeQueue feature flag and make the whole feature respect it:
useMergeQueueStatus, the sidebar's per-repo query and the GitHub panel's queue
tab were all polling Supabase regardless of the flag, and the panel's tab is
now hidden entirely when it is off.

Add a cloud-stored per-repo opt-in (007_merge_queue_enabled.sql) exposed as
get/set_merge_queue_enabled RPCs. A repo with no config row reads as OFF, so
the merge queue has to be switched on from the GitHub panel's Merge Queue tab
before 'Add to Queue' appears on a workspace or anything can be enqueued.

Merge Queue tab:
- toggle is a Switch rather than a button
- entries are listed as PRs and grouped into stacks, each stack rendered as a
  block that states it merges bottom-up into its target
- entries and whole stacks can be removed from the queue; removing a branch
  mid-stack takes everything above it so nothing is left stranded

Also fixes defects found in QA: QueueEntryStatus was missing the 'merging'
enum value and carried a 'passed' value the entry enum never emits, leaving
merging branches with an unstyled grey sidebar dot; enqueue only invalidated
the workspace's own status query, leaving sidebar dots and the queue tab stale
until their next 30s poll; and the header no longer surfaces a 'Testing…'
label.

Screenshot specs cover the queue states, the tab with multiple stacked PRs,
removal, and a flag-off regression guard.
…ntegrations

The per-repo merge queue toggle now lives in the settings page as a GitHub
setting alongside the repositories card, rather than in the GitHub panel's
Merge Queue tab. The tab keeps the disabled state but only reports it and
links to Settings; it no longer owns the control.

Also shortens the workspace header's queued label to 'Queued' instead of
'Queue #N'.
… continuous queue rail

Settings > Integrations now uses one header per integration with its settings
listed directly beneath, instead of bordered cards. The merge queue sits under
the GitHub header alongside the connected-repositories row.

The disabled state is a CTA rather than a prompt: an eligible repo (Pro plan,
GitHub App installed) gets a primary 'Enable merge queue' button, and the
switch only appears once the queue is on. An ineligible repo gets the reason
-- upgrade to Pro, or install the App on this repo -- so the control is never
offered where it could only fail.

The Merge Queue tab draws the queue as one continuous vertical rail with a
status-coloured node per entry, following the stack panel's idiom. The line
runs unbroken across stack groupings down to the target branch, showing that
the whole queue is a single merge sequence; stacks are marked by inline
headers and an accent on their entries rather than by separate cards.

Keys GitHubIntegrationSettings' repository-loading effect on user.id instead
of the auth objects, so a caller whose useAuth returns fresh objects per
render no longer loops.
Rebased onto main (Storybook setup, sparse checkout, font update, and the
new max-3-capture-document-expectations / no-inline-comments-in-tests /
no-multiline-inline-comments lint rules landed there since this branch was
cut). Rebase itself was conflict-free; this addresses what the newer lint
rules and CI now catch:

- Extract GitHubPanel's merge-queue rendering into
  github-panel/MergeQueueTab.tsx, bringing GitHubPanel.tsx under the 500-line
  cap.
- Rewrite useDequeueBranches' sequential dequeue loop without an
  await-in-loop, using a promise-chained reduce (ordering is still strictly
  top-down, which is why it can't be Promise.all).
- Fix max-params/max-nested-callbacks in merge-queue-stacks.test.ts by giving
  entry() an options object and extracting branchNames()/stackBranches()
  helpers instead of nesting .map() calls three deep.
- Use array destructuring instead of indexing in buildQueueStacks.
- Remove comments from the two merge-queue .test.ts files (banned there) and
  collapse a few multi-line comments elsewhere to one line.
- Split two captureDocument() calls whose expectations grew past 3 into
  separate captures with their own names.

Also rebuilt the NAPI addon, which was stale against main's sparse-checkout
Rust changes and was failing test/integration/workspace/sparse.test.ts.

No behavioral changes: full suite (281 tests) and the screenshot suite (25
captures across 14 spec files) pass, and I re-verified the merge-queue
screenshots pixel-by-pixel against their (now split) expectations.
@Ziinc
Ziinc force-pushed the claude/merge-queue-qa-9b2gmc branch from a2c3612 to 9fe3af7 Compare July 29, 2026 00:44
…ks.test.ts

npm run check:format (biome format ./src ./test) was failing CI on wrapping
that hadn't been run through the formatter after the file extraction/rewrite
in the previous commit.
@Ziinc Ziinc changed the title Implement merge queue feature with per-repo opt-in and UI feat: Implement merge queue feature with per-repo opt-in and UI Jul 29, 2026
@Ziinc
Ziinc merged commit 733c07a into main Jul 29, 2026
10 of 11 checks passed
@Ziinc
Ziinc deleted the claude/merge-queue-qa-9b2gmc branch July 29, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants